﻿using System;

namespace OS.Common.Utils
{
    public class NumberGenerate
    {
        private long _sequence;

        private const long MaxSequence = 4095L;

        private const int SequenceBitLength = 12;

        private const long MaxWorkerId = 1023L;

        private const int WorkerLeftShift = 12;

        private const int WorkerIdBitLength = 10;

        private const int TimestampLeftShift = 22;

        private long _lastTimestamp;

        /// <summary>
        /// 当前的工作Id最大值不能超过(2的11次方 - 1)
        /// </summary>
        public long WorkId { get; }

        public NumberGenerate(long workId)
        {
            if (workId <= 1023 && workId >= 0)
            {
                WorkId = workId;
                return;
            }
            throw new ArgumentException("workId", $"工作Id不能大于{workId}或者小于0！");
        }

        /// <summary>
        /// 生成下一个编号
        /// </summary>
        /// <returns></returns>
        public long NextNumber()
        {
            return GetTimestampAndSetSequence() << TimestampLeftShift | WorkId << WorkerLeftShift | _sequence;
        }

        /// <summary>
        /// 获取数字编号（排除机器位，直接是时间戳+自增序列）
        /// </summary>
        /// <returns></returns>
        internal long GetNumberWithoutWorker()
        {
            return GetTimestampAndSetSequence() << SequenceBitLength | _sequence;
        }

        private long GetTimestampAndSetSequence()
        {
            var number = NumberUtil.TimeMilliNumber();
            //如果当前时间小于上一次生成Id的时间戳，说明系统时间回退过，这个时间应当抛出异常
            if (number < _lastTimestamp)
            {
                throw new ArgumentException($"系统时间回退过，拒绝生成当前Id的{_lastTimestamp - number}毫秒时间戳");
            }

            if (_lastTimestamp == number)
            {
                _sequence = (_sequence + 1 & 0xFFF);
                if (_sequence == 0L)
                {
                    number = WaitNextMillis();
                }
            }
            else
            {
                _sequence = 0L;
            }

            _lastTimestamp = number;
            return number;
        }

        /// <summary>
        /// 当前毫秒内序列使用完，等待下一秒
        /// </summary>
        /// <returns></returns>
        protected long WaitNextMillis()
        {
            long number;
            do
            {
                number = NumberUtil.TimeMilliNumber();
            } while (number <= _lastTimestamp);

            return number;
        }
    }
}